package job

import (
	"errors"
	"golang.org/x/net/context"
	"time"
	"webook/internal/domain"
	"webook/internal/service"
)

// Executor 任务执行器
type Executor interface {
	Name() string
	Exec(ctx context.Context, job domain.Job) error
}

// LocalFuncExecutor 本地方法执行器
type LocalFuncExecutor struct {
	funcs map[string]func(ctx context.Context, job domain.Job) error
}

func NewLocalFuncExecutor() Executor {
	return &LocalFuncExecutor{}
}

func (l *LocalFuncExecutor) RegisterFunc(name string, f func(ctx context.Context, job domain.Job) error) {
	l.funcs[name] = f
}

func (l *LocalFuncExecutor) Name() string {
	return "local"
}

func (l *LocalFuncExecutor) Exec(ctx context.Context, job domain.Job) error {
	f, ok := l.funcs[job.Name]
	if !ok {
		return errors.New("方法未注册")
	}
	return f(ctx, job)
}

// Scheduler 调度器
type Scheduler struct {
	executors map[string]Executor
	svc       service.JobService
	timeout   time.Duration
}

func NewScheduler(svc service.JobService, timeout time.Duration) *Scheduler {
	return &Scheduler{
		svc:     svc,
		timeout: timeout,
	}
}

func (s *Scheduler) RegisterExecutor(name string, executor Executor) {
	s.executors[name] = executor
}

func (s *Scheduler) Schedule(ctx context.Context) error {
	for {
		if ctx.Err() != nil {
			// 说明取消调度
			return ctx.Err()
		}
		// 去抢占任务
		dbCtx, cancel := context.WithTimeout(context.Background(), s.timeout)
		job, err := s.svc.Preempt(dbCtx)
		cancel()
		if err != nil {
			// 没抢到 => 继续抢
			continue
		}
		// 开始调度执行
		executor, ok := s.executors[job.Executor]
		if !ok {
			// 找不到调度器 => 执行中断（也可以进行下一轮）
			continue
		}
		// 调度任务（异步）
		go func() {
			defer func() {
				// 重置下次任务执行时间
				resetNextTimeCtx, resetCancel := context.WithTimeout(context.Background(), time.Second*30)
				defer resetCancel()
				_ = s.svc.ResetNextTime(resetNextTimeCtx, job)
				// 取消任务
				_ = job.CancelFunc()
			}()
			er := executor.Exec(ctx, job)
			if er != nil {
				// 说明任务执行失败
				return
			}
		}()
	}
}
